package com.kusej.dbdocexport.utils;

import com.kusej.dbdocexport.annotation.ExcelTable;
import com.kusej.dbdocexport.annotation.ExcelTableHeader;
import org.apache.poi.ss.usermodel.Sheet;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yangkx
 * @Description excel导出公用类
 * @date 2021/10/18 14:51
 */
public class ExcelExportUtils {

    private ExcelExportUtils(){

    }

    private static class ExportParam {
        private String fieldName;
        private Class<?> fieldClass;
        private String commentName;
        private boolean needWidth;
        private int width;
        private int type;

        public ExportParam(String commentName,boolean needWidth,int width,int type,String fieldName, Class<?> fieldClass) {
            this.fieldName = fieldName;
            this.fieldClass = fieldClass;
            this.commentName = commentName;
            this.width = width;
            this.needWidth = needWidth;
            this.type = type;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public Class<?> getFieldClass() {
            return fieldClass;
        }

        public void setFieldClass(Class<?> fieldClass) {
            this.fieldClass = fieldClass;
        }

        public String getCommentName() {
            return commentName;
        }

        public void setCommentName(String commentName) {
            this.commentName = commentName;
        }

        public boolean isNeedWidth() {
            return needWidth;
        }

        public void setNeedWidth(boolean needWidth) {
            this.needWidth = needWidth;
        }

        public int getWidth() {
            return width;
        }

        public void setWidth(int width) {
            this.width = width;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return "ExportParam{" +
                    "fieldName='" + fieldName + '\'' +
                    ", fieldClass=" + fieldClass +
                    ", commentName='" + commentName + '\'' +
                    ", needWidth=" + needWidth +
                    ", width=" + width +
                    ", type=" + type +
                    '}';
        }
    }


    /**
     * 渲染数据 适合模板的情况 不会操作style
     * 特殊需求，比如统计行，请到自己业务层处理
     * @param sheet
     * @param dataList
     * @param voClass
     * @param <T>
     */
    public static <T> void listToSheet(Sheet sheet,List<T> dataList,Class<T> voClass){
        listToSheetWithStartRowNum(sheet, dataList, voClass, -1);
    }


    /**
     * 手动指定起始行 适用于分多次渲染的情况
     * @param sheet
     * @param dataList
     * @param voClass
     * @param dataStartRowNum 起始行
     * @param <T>
     */
    public static <T> void listToSheet(Sheet sheet,List<T> dataList,Class<T> voClass, int dataStartRowNum){
        listToSheetWithStartRowNum(sheet, dataList, voClass, dataStartRowNum);
    }


    /**
     * 列表数据渲染
     * @param sheet
     * @param dataList
     * @param voClass
     * @param dataStartRowNum -1时从注解取
     * @param <T>
     */
    private static <T> void listToSheetWithStartRowNum(Sheet sheet,List<T> dataList,Class<T> voClass, int dataStartRowNum){

        Map<Integer, ExportParam> indexFieldMap = checkExportExcel(sheet, voClass);

        ExcelTableHeader headerAnnotation = voClass.getAnnotation(ExcelTableHeader.class);
        String headerName = headerAnnotation.headerName();
        if (!headerName.isEmpty()){
            int headRowNum = headerAnnotation.headRowNum();
            int headLastRowNum = headerAnnotation.headRowLastNum();
            int headColumnNum = headerAnnotation.headColumnNum();
            int headColumnLastNum = headerAnnotation.headColumnLastNum();
            if (headColumnLastNum > headColumnNum && headLastRowNum >= headRowNum){
                ExcelUtils.cellRangeAdd(sheet,headRowNum,headLastRowNum,headColumnNum,headColumnLastNum);
            }
            ExcelUtils.setValue(sheet,headRowNum,headColumnNum, headerName,
                    null);
        }

        int columnSize = indexFieldMap.size();
        if(headerAnnotation.needCommentRow()){
            int commentRowNum = headerAnnotation.commentRowNum();
            int commentStartColumnNum = headerAnnotation.commentStartColumnNum();
            for (int i = commentStartColumnNum; i < columnSize; i++) {
                ExportParam exportParam = indexFieldMap.get(i);
                if (exportParam == null){
                    continue;
                }
                if (exportParam.isNeedWidth()){
                    ExcelUtils.setValue(sheet,commentRowNum,i
                            ,exportParam.getCommentName(),null,exportParam.getWidth());
                }else {
                    ExcelUtils.setValue(sheet,commentRowNum,i
                            ,exportParam.getCommentName(),null);
                }
            }
        }

        int dataSize = dataList.size();
        if (dataSize == 0){
            return;
        }
        if (dataStartRowNum == -1){
            dataStartRowNum = headerAnnotation.dataStartRowNum();
        }
        int dataLastRowNumSize = dataStartRowNum + dataSize;
        int dataStartColumnNum = headerAnnotation.dataStartColumnNum();
        int dataLastColumnNumSize = dataStartColumnNum + columnSize;
        int dataIndex = 0;
        for (int i = dataStartRowNum; i < dataLastRowNumSize; i++) {
            for (int j = dataStartColumnNum; j < dataLastColumnNumSize; j++) {
                ExportParam exportParam = indexFieldMap.get(j);
                if (exportParam == null){
                    continue;
                }
                T t = dataList.get(dataIndex);
                setValueByType(sheet,t,exportParam,i,j);
            }
            dataIndex ++ ;
        }
    }

    /**
     * 检查导出设置是否完整
     * @param sheet
     * @param voClass
     * @param <T>
     * @return 下标 导出参数
     */
    private static <T> Map<Integer, ExportParam> checkExportExcel(Sheet sheet, Class<T> voClass){
        Map<Integer, ExportParam> indexFieldMap = new HashMap<>(32);
        ExcelTableHeader headerAnnotation = voClass.getAnnotation(ExcelTableHeader.class);
        if(headerAnnotation == null){
            throw new RuntimeException("注解缺失啦T^T 需要@ExcelTableHeader");
        }
        boolean allNeedWidth = headerAnnotation.needColumnWidth();
        Field[] voFields = ReflectUtil.getFields(voClass);
        for (int i = 0; i < voFields.length; i++) {
            Field field = voFields[i];
            ExcelTable excelAnno = field.getAnnotation(ExcelTable.class);
            if (excelAnno != null){
                String commentName = excelAnno.value();
                int index = excelAnno.colIndex() < 0 ? i : excelAnno.colIndex();
                boolean needWidth = allNeedWidth && excelAnno.needWidth();
                int width = excelAnno.width();
                int type = excelAnno.type();
                String fieldName = field.getName();
                indexFieldMap.put(index,new ExportParam(commentName,needWidth,width,type,fieldName,field.getType()));
            }
        }
        //如果文件里的列不能完全包含注解的列 那么认为文件有问题
        if (indexFieldMap.size() == 0){
            throw new RuntimeException("注解缺失啦T^T 需要至少一个@Excel");
        }
        return indexFieldMap;
    }



    /**
     * 处理指定类型情况的字符串
     * @param sheet
     * @param object
     * @param exportParam
     * @param rowNum
     * @param columnNum
     */
    private static void setValueByType(Sheet sheet,Object object,ExportParam exportParam,int rowNum,int columnNum){
        Object fieldValue = ReflectUtil.getFieldValue(object, exportParam.getFieldName());
        int width = exportParam.getWidth();
        int type = exportParam.getType();
        if (fieldValue == null){
            if (exportParam.isNeedWidth()){
                ExcelUtils.setValue(sheet,rowNum,columnNum, null,null,width);
            }else {
                ExcelUtils.setValue(sheet,rowNum,columnNum, null,null);
            }
            return;
        }
        if (type == 0 || type > 2){
            if (exportParam.isNeedWidth()){
                ExcelUtils.setValue(sheet,rowNum,columnNum,fieldValue,null,width);
            }else {
                ExcelUtils.setValue(sheet,rowNum,columnNum,fieldValue,null);
            }
            return;
        }
        switch (type){
            //强制字符串
            case 1:{
                String string;
                if (fieldValue instanceof String){
                    string = fieldValue.toString();
                }else if (fieldValue instanceof BigDecimal){
                    string = ((BigDecimal) fieldValue).toPlainString();
                }else if (fieldValue instanceof LocalDate){
                    LocalDate localDate = ((LocalDate) fieldValue);
                    string = DateTimeUtils.format2WebDate(localDate);
                }else if (fieldValue instanceof LocalDateTime){
                    LocalDateTime localDateTime = ((LocalDateTime) fieldValue);
                    string = DateTimeUtils.format2WebDateTime(localDateTime);
                }else {
                    string = fieldValue.toString();
                }

                if (exportParam.isNeedWidth()){
                    ExcelUtils.setValue(sheet,rowNum,columnNum,string,null,width);
                }else {
                    ExcelUtils.setValue(sheet,rowNum,columnNum,string,null);
                }
                return;
            }
            //强制数字
            case 2:{
                if (fieldValue instanceof Number){

                    if (exportParam.isNeedWidth()){
                        ExcelUtils.setValue(sheet,rowNum,columnNum,fieldValue,null,width);
                    }else {
                        ExcelUtils.setValue(sheet,rowNum,columnNum,fieldValue,null);
                    }
                }else {
                    String numStr = fieldValue.toString();
                    BigDecimal decimal;
                    try {
                        decimal = new BigDecimal(numStr);
                    }catch (Exception e){
                        e.printStackTrace();
                        throw new RuntimeException("数字转换格式异常" + " " + numStr);
                    }

                    if (exportParam.isNeedWidth()){
                        ExcelUtils.setValue(sheet,rowNum,columnNum,decimal,null,width);
                    }else {
                        ExcelUtils.setValue(sheet,rowNum,columnNum,decimal,null);
                    }
                }
                return;
            }
            default:
        }
    }
}
